Victor Gómez Gamero
Marc Portavella Boixader
El siguiente informe recoge un estudio de datos para la asignatura data driven security. Para ello se ha escogido la temática de threat intelligence donde se analizan datos sobre IPs maliciosas.
Con este estudio se pretende llevar a cabo una primera toma de contacto con un conjunto de datos bastante grande sobre Ips maliciosas y ver que patrones o observaciones se pueden sacar, a su vez ver posibles aplicaciones para realizar predicciones.
Los datos se han recogido de distintas fuentes para poder tener información complementaria:
En el github de firehol encontramos un conjunto de ficheros que contienen blacklists mantenidas por terceros. En ellas encontramos las ips maliciosas identificadas diariamente y clasificadas segun su categoria.
En la web de maxmind encontramos una base de datos gratuita con la información de geolocalización de las ips. A su vez proporcionan una api para trabajar con la base de datos.
En la web de worldbank encontramos datos demográficos a nivel mundial.
A continuación definimos las funciones utilizadas para el tratamiento de datos. Hay que tener en cuenta que la función getdataframe tiene bastante coste temporal, por ello, es preferible guardar el dataframe en disco para luego cargarlo.
import os
import glob
import pandas
import geoip2.database as db
import datetime
from plotnine import *
import matplotlib.pyplot as plt
import plotly as py
import plotly.express as px
import plotly.graph_objects as go
pandas.options.display.max_columns = 10
def getdataframe(source,dest = ""):
files = glob.glob(os.path.join(source,"*.ipset"))
dftotal = pandas.DataFrame(columns=["IP","Category","Maintainer","Country","Date"])
reader = db.Reader(r"./GeoLite2-Country_20191210/GeoLite2-Country.mmdb")
for f in files:
ips = []
countrylist = []
cat = ""
main = ""
date = ""
df = pandas.DataFrame(columns=["IP","Category","Maintainer","Country","Date"])
aux = open(f,"r")
content = aux.readlines()
aux.close()
for l in content:
if "Category" in l:
cat = str(l.split(":")[-1]).strip()
elif "Maintainer" in l:
main = str(l.split(":")[-1]).strip().replace("/","")
elif "#" not in l:
ips.append(str(l).strip())
elif "This File Date" in l:
date = str(l.split(" : ")[-1]).strip().replace(" "," ")
try:
date = str(datetime.datetime.strptime(date, "%a %b %d %H:%M:%S UTC %Y"))
except:
print(str(l))
df["IP"] = ips
df["Category"] = [cat]*len(ips)
df["Maintainer"] = [main]*len(ips)
df["Date"] = [date]*len(ips)
for ip in df.IP.values:
try:
c = reader.country(ip).country.names["en"]
except:
c = ''
countrylist.append(c)
df["Country"] = countrylist
dftotal = dftotal.append(df, ignore_index = True)
if dest != "" and not os.path.exists(dest):
dftotal.to_csv(dest,index=False)
return dftotal
def dropduplicates(dataframe,groupcolumns):
aux = dataframe.copy()
aux["Occurrence"] = df.groupby(groupcolumns).cumcount() + 1
return aux.drop_duplicates(subset = groupcolumns, keep = "last")
def correlation(dataframe1,dataframe2):
normalized = []
country = []
for c in dataframe2.columns[2:]:
try:
a = int(dataframe2[c])
b = int(dataframe1.loc[dataframe1["Country Name"] == c]["2018"])
normalized.append((a/b)*1000)
country.append(c)
except:
pass
aux = pandas.DataFrame(columns=["Country","Risk"])
aux["Country"] = country
aux["Risk"] = normalized
return aux
def correlation2(dataframe):
it = dataframe.iterrows()
try:
next(it)
country = []
category = []
for i in range(len(dataframe.index)):
aux = next(it)
country.append(aux[0])
a1 = aux[1].keys().to_list()
a2 = list(aux[1].values)
c = a1[a2.index(max(a2))]
category.append(c)
except:
pass
dfret = pandas.DataFrame(columns=["Country","Category"])
dfret["Country"] = country
dfret["Category"] = category
return dfret
def correlation3(df3,df4,df6,df7):
#Es creen llistes buides que anem omplint amb les dades que ens interesen
normalized = []
country = []
code = []
category = []
category2 = []
for c in df4.columns[2:]:
try:
a = int(df4[c])
b = int(df3.loc[df3["Country Name"] == c]["2018"])
normalized.append((a/b)*1000)
country.append(c)
#Es fa una cerca per trobar el país amb el que coincideix
#i treure l'iterador per introduir el codi correcte
s = -1
for cou in df3.iloc[0:,0]:
s = s+1
if cou == c:
code.append(df3.at[s, 'Country Code'])
it = -1
for i in df6.iloc[0:,0]:
it = it+1
if i == c:
category.append(df6.at[it, 'Category'])
it2 = 0
t = False
for i2 in df7.iloc[0:,0]:
if i2 == c:
category2.append(df7.at[it2, 'Category'])
t = True
it2 = it2+1
if it2 == 225:
if t == False:
category2.append("No data")
t = False
except:
pass
aux = pandas.DataFrame(columns=["Country","Risk", "Code", "Category","No Abuse"])
aux["Country"] = country
aux["Risk"] = normalized
aux["Code"] = code
aux["Category"] = category
aux["No Abuse"] = category2
return aux
Los datos obtenidos, se han parseado y limpiado para poder sacar la información relevante. De firehol se ha extraido la siguiente información : IP, Categoría, Quien mantiene la lista donde aparece dicha ip y fecha de la lista. De maxmind hemos obtenido la geolocalización de cada ip mediante su API. Hay que tener en cuenta que de los datos en bruto se podía extraer más información que no nos ha parecido relevante para este estudio. Con lo mencionado se ha generado el siguiente dataframe con toda la información parseada. La primera linea de código genera de nuevo el dataframe, con la segunda, cargamos uno preguardado.
#df = getdataframe(r".\blocklist-ipsets-master")
df = pandas.read_csv(r".\brutedataframe_country_date.csv")
df
| IP | Category | Maintainer | Country | Date | |
|---|---|---|---|---|---|
| 0 | 1.0.104.86 | reputation | www.alienvault.com | Japan | 2019-12-16 10:52:34 |
| 1 | 1.0.130.91 | reputation | www.alienvault.com | Thailand | 2019-12-16 10:52:34 |
| 2 | 1.0.130.96 | reputation | www.alienvault.com | Thailand | 2019-12-16 10:52:34 |
| 3 | 1.0.130.127 | reputation | www.alienvault.com | Thailand | 2019-12-16 10:52:34 |
| 4 | 1.0.130.163 | reputation | www.alienvault.com | Thailand | 2019-12-16 10:52:34 |
| ... | ... | ... | ... | ... | ... |
| 2773363 | 222.186.36.58 | organizations | pgl.yoyo.orgadservers | China | 2019-12-13 13:40:03 |
| 2773364 | 222.187.221.28 | organizations | pgl.yoyo.orgadservers | China | 2019-12-13 13:40:03 |
| 2773365 | 222.236.44.131 | organizations | pgl.yoyo.orgadservers | South Korea | 2019-12-13 13:40:03 |
| 2773366 | 223.165.25.36 | organizations | pgl.yoyo.orgadservers | India | 2019-12-13 13:40:03 |
| 2773367 | 223.165.31.15 | organizations | pgl.yoyo.orgadservers | India | 2019-12-13 13:40:03 |
2773368 rows × 5 columns
Por último con la información de worldbank hemos generado un dataframe que contiene información relevante por país.
df1 = pandas.read_csv(r".\API_SP.POP.TOTL_DS2_en_csv_v2_566132.csv")
df1
| Country Name | Country Code | Indicator Name | Indicator Code | 1960 | ... | 2016 | 2017 | 2018 | 2019 | Unnamed: 64 | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Aruba | ABW | Population, total | SP.POP.TOTL | 54211.0 | ... | 104872.0 | 105366.0 | 105845.0 | NaN | NaN |
| 1 | Afghanistan | AFG | Population, total | SP.POP.TOTL | 8996973.0 | ... | 35383128.0 | 36296400.0 | 37172386.0 | NaN | NaN |
| 2 | Angola | AGO | Population, total | SP.POP.TOTL | 5454933.0 | ... | 28842484.0 | 29816748.0 | 30809762.0 | NaN | NaN |
| 3 | Albania | ALB | Population, total | SP.POP.TOTL | 1608800.0 | ... | 2876101.0 | 2873457.0 | 2866376.0 | NaN | NaN |
| 4 | Andorra | AND | Population, total | SP.POP.TOTL | 13411.0 | ... | 77297.0 | 77001.0 | 77006.0 | NaN | NaN |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 259 | Kosovo | XKX | Population, total | SP.POP.TOTL | 947000.0 | ... | 1816200.0 | 1830700.0 | 1845300.0 | NaN | NaN |
| 260 | Yemen, Rep. | YEM | Population, total | SP.POP.TOTL | 5315355.0 | ... | 27168210.0 | 27834821.0 | 28498687.0 | NaN | NaN |
| 261 | South Africa | ZAF | Population, total | SP.POP.TOTL | 17099840.0 | ... | 56203654.0 | 57000451.0 | 57779622.0 | NaN | NaN |
| 262 | Zambia | ZMB | Population, total | SP.POP.TOTL | 3070776.0 | ... | 16363507.0 | 16853688.0 | 17351822.0 | NaN | NaN |
| 263 | Zimbabwe | ZWE | Population, total | SP.POP.TOTL | 3776681.0 | ... | 14030390.0 | 14236745.0 | 14439018.0 | NaN | NaN |
264 rows × 65 columns
Inicialmente se ha trabajado en un primer estudio solo con los datos de un solo día obtenidos de firehol. Con ello se ha querido realizar observaciones a pequeña escala antes de pasar a grandes conjuntos de datos.
Se ha observado como se reparten las ips maliciosas en las distintas categorías definidas por firehol.
A continuación se ha transformado la información ya limpia para representar correctamente lo que se quería observar. Para ello se ha usado la información recopilada en el datafrme df. Dado que df contenía ips repetidas en la misma categoría, se han llevado a cabo distintos pasos para agrupar la información de forma conveniente.
En primer lugar, se ha agrupado toda la información del dataframe por IP/categoría y a continuación se han eliminado los duplicados.
df2 = dropduplicates(df,["IP","Category"])
df2
| IP | Category | Maintainer | Country | Date | Occurrence | |
|---|---|---|---|---|---|---|
| 0 | 1.0.104.86 | reputation | www.alienvault.com | Japan | 2019-12-16 10:52:34 | 1 |
| 1 | 1.0.130.91 | reputation | www.alienvault.com | Thailand | 2019-12-16 10:52:34 | 1 |
| 2 | 1.0.130.96 | reputation | www.alienvault.com | Thailand | 2019-12-16 10:52:34 | 1 |
| 3 | 1.0.130.127 | reputation | www.alienvault.com | Thailand | 2019-12-16 10:52:34 | 1 |
| 4 | 1.0.130.163 | reputation | www.alienvault.com | Thailand | 2019-12-16 10:52:34 | 1 |
| ... | ... | ... | ... | ... | ... | ... |
| 2773363 | 222.186.36.58 | organizations | pgl.yoyo.orgadservers | China | 2019-12-13 13:40:03 | 1 |
| 2773364 | 222.187.221.28 | organizations | pgl.yoyo.orgadservers | China | 2019-12-13 13:40:03 | 1 |
| 2773365 | 222.236.44.131 | organizations | pgl.yoyo.orgadservers | South Korea | 2019-12-13 13:40:03 | 2 |
| 2773366 | 223.165.25.36 | organizations | pgl.yoyo.orgadservers | India | 2019-12-13 13:40:03 | 1 |
| 2773367 | 223.165.31.15 | organizations | pgl.yoyo.orgadservers | India | 2019-12-13 13:40:03 | 2 |
1373455 rows × 6 columns
Por último, se ha pivotado la información para quedara una tabla representado lo que se quería observar y se ha realizado una agregado por columnas.
df3 = df2.pivot(index = "IP", columns = "Category", values = "Occurrence")
df3
| Category | abuse | anonymizers | attacks | malware | organizations | reputation | spam |
|---|---|---|---|---|---|---|---|
| IP | |||||||
| 0.0.0.1 | NaN | NaN | NaN | 1.0 | NaN | NaN | NaN |
| 0.14.1.6 | NaN | NaN | NaN | NaN | 4.0 | NaN | NaN |
| 0.15.0.1 | NaN | NaN | NaN | NaN | 4.0 | NaN | NaN |
| 0.15.6.5 | NaN | NaN | NaN | NaN | 4.0 | NaN | NaN |
| 0.15.9.1 | NaN | NaN | NaN | NaN | 4.0 | NaN | NaN |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 99.99.178.63 | 4.0 | NaN | NaN | NaN | NaN | NaN | NaN |
| 99.99.203.59 | 2.0 | NaN | NaN | NaN | NaN | NaN | NaN |
| 99.99.253.228 | NaN | NaN | NaN | NaN | 1.0 | NaN | NaN |
| 99.99.38.14 | 1.0 | NaN | NaN | NaN | NaN | NaN | NaN |
| 99.99.60.120 | NaN | NaN | 1.0 | NaN | NaN | NaN | NaN |
1306224 rows × 7 columns
a = df3.agg("sum")
a
Category
abuse 1383986.0
anonymizers 133325.0
attacks 538736.0
malware 185034.0
organizations 91017.0
reputation 236887.0
spam 204383.0
dtype: float64
Con los datos obtenidos en distintas etapas del tratamiento, podemos representar de varias formas la distribución según categoría:
ggplot(df, aes(x="Category")) + geom_bar(stat = 'count')
<ggplot: (97737331250)>
def donutplot(a):
fig = plt.figure()
fig.patch.set_facecolor("white")
plt.rcParams["text.color"] = "black"
my_circle = plt.Circle((0,0), 0.7, color="white")
plt.pie(a.values,labels=df3.columns)
p = plt.gcf()
p.gca().add_artist(my_circle)
return p
p = donutplot(a)
Los datos obtenidos se han utilizado para observar y calcular un índice de peligrosidad por país. Para conseguir dicho índice se han usado los datos de df y df3. Para poder representar la información correctamente, se ha generado un nuevo dataframe que contiene el país con su índice calculado en base a nº IPs maliciosas / Población.
El dataframe df4 contiene la distribución de IPs por país.
df4 = dropduplicates(df,["IP"])
df4 = df4.pivot(index = "IP", columns = "Country", values = "Occurrence")
df4 = df4.agg(["sum"])
df4
| NaN | Afghanistan | Albania | Algeria | American Samoa | ... | Vietnam | Yemen | Zambia | Zimbabwe | Åland | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| sum | 10521.0 | 392.0 | 3205.0 | 6689.0 | 25.0 | ... | 105709.0 | 365.0 | 558.0 | 495.0 | 22.0 |
1 rows × 242 columns
Finalmente en el dataframe df5 podemos ver la correlación, esta nos proporciona un indice de riesgo Risk.
df5 = correlation(df1,df4)
df5
| Country | Risk | |
|---|---|---|
| 0 | Albania | 1.118137 |
| 1 | Algeria | 0.158400 |
| 2 | American Samoa | 0.450735 |
| 3 | Andorra | 3.337402 |
| 4 | Angola | 0.017300 |
| ... | ... | ... |
| 175 | Uzbekistan | 0.045759 |
| 176 | Vanuatu | 0.116168 |
| 177 | Vietnam | 1.106433 |
| 178 | Zambia | 0.032158 |
| 179 | Zimbabwe | 0.034282 |
180 rows × 2 columns
Una vez hemos encontrado este índice de peligrosidad, hemos pensado en añadir también una observación que contemplase la categoría mas repetida en cada país. Esto junto con el índice, te da más información aún para atribuir un valor de peligro a cada país, ya que no es lo mismo mantener ips categorizadas por distribución de spam que categorizadas por distribución de malware. Para ello hemos agrupado el numero de IPs de cada categoría que mantenía cada país y el más grande es el que hemos anotado.
Una vez hecho el agregado nos ha quedado el siguiente dataframe.
dfcat = df.copy()
df6 = dropduplicates(dfcat,["Category","Country"])
df6 = df6.pivot(index = "Country", columns = "Category", values = "Occurrence")
df6 = correlation2(df6)
df6
| Country | Category | |
|---|---|---|
| 0 | Afghanistan | abuse |
| 1 | Albania | abuse |
| 2 | Algeria | abuse |
| 3 | American Samoa | abuse |
| 4 | Andorra | malware |
| ... | ... | ... |
| 236 | Vietnam | abuse |
| 237 | Yemen | abuse |
| 238 | Zambia | abuse |
| 239 | Zimbabwe | abuse |
| 240 | Åland | abuse |
241 rows × 2 columns
El problema que se puede observar es que la gran mayoría de bloques de cada país es de abuse por lo tanto, viendo que se repetía mucho, hemos optado a hacer la lista sin contar esta categoría de cara a mostrar otros datos relevantes como es el segundo bloque mas mantenido.
dfcat = df.copy()
dfcat = df[df.Category != "abuse"]
df7 = dropduplicates(dfcat,["Category","Country"])
df7 = df7.pivot(index = "Country", columns = "Category", values = "Occurrence")
df7 = correlation2(df7)
df7
| Country | Category | |
|---|---|---|
| 0 | Afghanistan | attacks |
| 1 | Albania | anonymizers |
| 2 | Algeria | spam |
| 3 | American Samoa | anonymizers |
| 4 | Andorra | malware |
| ... | ... | ... |
| 220 | Vietnam | spam |
| 221 | Yemen | anonymizers |
| 222 | Zambia | attacks |
| 223 | Zimbabwe | attacks |
| 224 | Åland | anonymizers |
225 rows × 2 columns
Una vez obtenidos todos los datos estructurados en distintos bloques, se ha procedido a coordinar los diversos dataframes. Mayormente, el problema que hemos tenido es que cada dataframe ponía los países de una forma diferente o ponía más o menos países por lo que ha sido necesario realizar una reestructuración y normalización de la información.
Así que la solución ha sido hacer un script para buscar las coincidencias y así escribir un dataframe final con esto. evitando así tener que construir nosotros unos dataframes idénticos durante las distintas observaciones y sólo tener que invertir tiempo para la construcción del dataframe final para mostrarlo gráficamente.
A continuación, como se ha comentado anteriormente se muestra la normalización realizada en los nombres de los países para poder trabajar con los distintos dataframes:
def rewrite(dataframe):
d = {'Venezuela, RB':'Venezuela',"Egypt, Arab Rep.":"Egypt","Korea, Rep.":"South Korea","Iran, Islamic Rep.":"Iran","Yemen, Rep.":"Yemen",
"Czech Republic":"Czechia","Syrian Arab Republic":"Syria","Congo, Rep.":"Congo Republic",
"Slovak Republic":"Slovakia","Lithuania":"Republic of Lithuania","Moldova":"Republic of Moldova",
"Kyrgyz Republic":"Kyrgyzstan","Lao PDR":"Laos","Jordan":"Hashemite Kingdom of Jordan","Congo, Dem. Rep.":"DR Congo",
"Cote d'Ivoire":"Ivory Coast","Seychelles":"Sey","British Virgin Islands":"BR","Russian Federation":"Russia"}
dataframe = dataframe.replace(d)
dataframe.drop(dataframe.columns[len(dataframe.columns)-1],axis=1,inplace=True)
return dataframe
El último problema que hemos afrontado ya de cara a normalizar los colores del mapa y adelantandonos un poco a la conclusión, es que algunos de los países que tienen bastantes bloques maliciosos no estaban representados en ningun mapa de cloropeth debido a ser paraísos fiscales muy pequeños y negligibles como puede ser Seychelles o Virgin Islands, así que los hemos tenido que retirar por la falta de datos.
Finalmente, una vez agrupado todo en un dataframe, hemos aplicado esta información para construir un modelo de mapa de cara a mostrar visualmente la cantidad de bloques maliciosos de IPs por población.
El resultado es el siguiente dataframe:
dataframe final
df1rw = rewrite(df1)
df8 = correlation3(df1rw,df4,df6,df7)
df8
| Country | Risk | Code | Category | No Abuse | |
|---|---|---|---|---|---|
| 0 | Albania | 1.118137 | ALB | abuse | anonymizers |
| 1 | Algeria | 0.158400 | DZA | abuse | spam |
| 2 | American Samoa | 0.450735 | ASM | abuse | anonymizers |
| 3 | Andorra | 3.337402 | AND | malware | malware |
| 4 | Angola | 0.017300 | AGO | abuse | spam |
| ... | ... | ... | ... | ... | ... |
| 190 | Venezuela | 0.212780 | VEN | abuse | attacks |
| 191 | Vietnam | 1.106433 | VNM | abuse | spam |
| 192 | Yemen | 0.012808 | YEM | abuse | anonymizers |
| 193 | Zambia | 0.032158 | ZMB | abuse | attacks |
| 194 | Zimbabwe | 0.034282 | ZWE | abuse | attacks |
195 rows × 5 columns
El mapa esta hecho con la librería plotly. A partir de los códigos que hemos añadido en los dataframes y en base al dataframe final, se ha generado un mapa interactivo que nos permite ver como de peligroso es un país.
def map():
dfmap = df8.copy()
dfmap['text'] = dfmap['Country'] + '<br>' + dfmap['Code'] + '<br>' + 'Main category: ' + dfmap['Category'] + '<br>' + 'Category withouth abuse: ' + dfmap['No Abuse']
fig = go.Figure(data=go.Choropleth(
locations = dfmap['Code'],
z = dfmap['Risk'],
text = dfmap['text'],
colorscale = 'Reds',
autocolorscale=False,
reversescale=False,
marker_line_color='darkgray',
marker_line_width=0.5,
colorbar_tickprefix = '',
colorbar_title = 'number of risk',
))
fig.update_layout(
title_text='Daily Global Risk',
geo=dict(
showframe=False,
showcoastlines=False,
projection_type='equirectangular'
),
annotations = [dict(
x=0.55,
y=0.1,
xref='paper',
yref='paper',
text='Source: Data Driven Class',
showarrow = False
)]
)
py.offline.plot(fig,filename="mapa.html")
map()
El resultado que podemos observar al final es bastante obvio con respecto al mapa. Los países que son paraísos fiscales como Belize, Seychelles o Virgin Islands por ejemplo, mantienen un número muy elevado de bloques maliciosos en relación a su población.
Seguidamente el segundo grupo con más bloques maliciosos es el soviético, y también se puede remarcar Islandia y Holanda que se unen a este bloque.
Se puede intuir que la causa es que estos países al tener una legislación más laxa, son una oportunidad para la gente que le interese mantener bloques de IPs que pueden servir para atacar al resto de países ya que así evitaran controles y repercusiones legales.
Una vez terminado el trabajo que la información que nos proporcionaba un día, se ha querido atacar mas datos para entrar en el concepto de las evoluciones temporales.
El estudio que hemos generado a partir del dataset de dos meses es algo diferente. En este caso, partiendo de las IPs de dos meses, lo que hemos hecho es coger todos los días en activo que ha tenido cada IP reportada como maliciosa y agruparlo por país de cara a ver que países podían acumular mas días con bloques maliciosos reportados y confirmados. Con ello podemos ver que mantenimiento se da en cada país a este tipo de IPs.
Los datos sobre los que se ha trabajado son los proporcionados en la asignatura:
df = pandas.read_csv(r".\df2month.csv", encoding = "ISO-8859-1")
df
| date | ioc | category | descr | mnt | |
|---|---|---|---|---|---|
| 0 | 2019-10-15 02:48:08 | 1.0.69.58 | reputation | IP reputation database | Alien Vault |
| 1 | 2019-10-15 02:48:08 | 1.0.76.41 | reputation | IP reputation database | Alien Vault |
| 2 | 2019-10-15 02:48:08 | 1.0.84.116 | reputation | IP reputation database | Alien Vault |
| 3 | 2019-10-15 02:48:08 | 1.0.116.165 | reputation | IP reputation database | Alien Vault |
| 4 | 2019-10-15 02:48:08 | 1.0.118.213 | reputation | IP reputation database | Alien Vault |
| ... | ... | ... | ... | ... | ... |
| 2734866 | 2019-10-11 21:08:04 | 222.186.36.58 | organizations | IPs of ad servers | Yoyo.org |
| 2734867 | 2019-10-11 21:08:04 | 222.187.221.28 | organizations | IPs of ad servers | Yoyo.org |
| 2734868 | 2019-10-11 21:08:04 | 222.236.44.131 | organizations | IPs of ad servers | Yoyo.org |
| 2734869 | 2019-10-11 21:08:04 | 223.165.25.36 | organizations | IPs of ad servers | Yoyo.org |
| 2734870 | 2019-10-11 21:08:04 | 223.165.31.15 | organizations | IPs of ad servers | Yoyo.org |
2734871 rows × 5 columns
Concretamente en este caso hemos utilizado 4 funciones y hemos generado 4 dataframes distintos hasta llegar al final que hemos utilizado para generar este último mapa.
Primeramente hemos hecho el agregado de cuantos días ha estado una IP activa en estos dos meses
def days_repited(df):
#Para sacar dias en total que una IP que se ha tenido en activo en dos meses
count_series = df.groupby(['ioc']).size()
return count_series.to_frame(name = 'Dias').reset_index()
Esto nos permite ver cuantos días se ha repetido esta IP como podemos ver en esta tabla:
df9 = days_repited(df)
df9
| ioc | Dias | |
|---|---|---|
| 0 | 0.0.0.1 | 1 |
| 1 | 0.0.10.40 | 3 |
| 2 | 0.0.10.42 | 3 |
| 3 | 0.0.10.44 | 3 |
| 4 | 0.0.10.45 | 3 |
| ... | ... | ... |
| 1339746 | 99.99.139.67 | 3 |
| 1339747 | 99.99.203.59 | 4 |
| 1339748 | 99.99.253.228 | 1 |
| 1339749 | 99.99.38.14 | 2 |
| 1339750 | 99.99.60.120 | 1 |
1339751 rows × 2 columns
Seguidamente hemos pensado que podíamos analizar de estos datos así que hemos decidido agrupar estas IPs por países de cara a consultar si los bloques reportados maliciosos solían aguantar mucho o no.
Para ello primero hemos tenido que averiguar el país de cada IP, así que hemos usado la siguiente función.
def country(df1,dest = ""):
# Sacamos el pais de cada IP maliciosa
df = pandas.DataFrame(columns=["IP","Days","Country"])
dftotal = pandas.DataFrame(columns=["IP","Days","Country"])
reader = db.Reader(r"./GeoLite2-Country_20191210/GeoLite2-Country.mmdb")
ips = []
countrylist = []
days = []
s = 0
for ip in df1.iloc[0:,0]:
try:
c = reader.country(ip).country.names["en"]
countrylist.append(c)
ips.append(df1.at[s, 'ioc'])
days.append(df1.at[s, 'Dias'].astype(int))
except:
#c = ''
pass
s = s +1
df["IP"] = ips
df["days"] = days
df["Country"] = countrylist
dftotal = dftotal.append(df, ignore_index = True)
if dest != "" and not os.path.exists(dest):
dftotal.to_csv(dest,index=False)
return dftotal
Con el país, hemos hecho el agregado para ver cuantos días acumulaban en total a partir de la siguiente función.
def agg_country(df):
# Agrupamos en total cuantos dias acumula cada pais de IPs maliciosas. Para saber si
# ese pais por ejemplo elimina rapido sus IPs reportadas como maliciosas
count_series = df.groupby(['Country']).size()
new_df = count_series.to_frame(name = 'Dias totales').reset_index()
return new_df
El resultado es bastante interesante ya que se podía ver mucha diferencia en la acumulación por país
El dataframe generado es el siguiente:
#df10 = country(df9,dest= r"./IP_Days_total.csv")
df10 = pandas.read_csv(r"./IP_Days_total.csv")
df10 = agg_country(df10)
df10
| Country | Dias totales | |
|---|---|---|
| 0 | Afghanistan | 225 |
| 1 | Albania | 1365 |
| 2 | Algeria | 4870 |
| 3 | American Samoa | 7 |
| 4 | Andorra | 111 |
| ... | ... | ... |
| 239 | Wallis and Futuna | 1 |
| 240 | Yemen | 185 |
| 241 | Zambia | 420 |
| 242 | Zimbabwe | 420 |
| 243 | Åland | 9 |
244 rows × 2 columns
Hemos tenido que generar otra vez el código para el uso del mapa con la siguiente función
def code(df,df5):
# Para meter el codigo ya que sino el mapa no se genera.
s = -1
country = []
days = []
code = []
s1 = -1
for c in df.iloc[0:,0]:
s = -1
s1 = s1 +1
try:
for cou in df5.iloc[0:,0]:
s = s+1
if cou == c:
#print (df5.at[s, 'Country Code'],df5.at[s, 'Country Name'])
code.append(df5.at[s, 'Country Code'])
country.append(df5.at[s, 'Country Name'])
days.append(df.at[s1, 'Dias totales'])
except:
pass
aux = pandas.DataFrame(columns=["Country","Dias totales", "Code"])
aux["Country"] = country
aux["Dias totales"] = days
aux["Code"] = code
#El dataframe final usado para el mapa es este
return aux
El dataframe final usado para el mapa es el siguiente:
df11 = code(df10,df1rw)
df11
| Country | Dias totales | Code | |
|---|---|---|---|
| 0 | Afghanistan | 225 | AFG |
| 1 | Albania | 1365 | ALB |
| 2 | Algeria | 4870 | DZA |
| 3 | American Samoa | 7 | ASM |
| 4 | Andorra | 111 | AND |
| ... | ... | ... | ... |
| 192 | Venezuela | 3590 | VEN |
| 193 | Vietnam | 72668 | VNM |
| 194 | Yemen | 185 | YEM |
| 195 | Zambia | 420 | ZMB |
| 196 | Zimbabwe | 420 | ZWE |
197 rows × 3 columns
Este mapa nos muestra el numero total de días acumulados por cada país de sus IPs maliciosas reportadas en estos dos meses del dataframe.
def map2():
dfmap = df11.copy()
dfmap['text'] = dfmap['Country'] + '<br>' + dfmap['Code']
fig2 = go.Figure(data=go.Choropleth(
locations = dfmap['Code'],
z = dfmap['Dias totales'],
text = dfmap['text'],
colorscale = 'Blues',
autocolorscale=False,
reversescale=False,
marker_line_color='darkgray',
marker_line_width=0.5,
colorbar_tickprefix = '',
colorbar_title = 'Dias totales',
))
fig2.update_layout(
title_text='Total de dias que han estado las IPs reportadas como maliciosas activas en dos meses',
geo=dict(
showframe=False,
showcoastlines=False,
projection_type='equirectangular'
),
annotations = [dict(
x=0.55,
y=0.1,
xref='paper',
yref='paper',
text='Source: Data Driven Class',
showarrow = False
)]
)
py.offline.plot(fig2,filename="mapa22.html")
map2()
Las conclusiones que podemos sacar de este mapa son bastante curiosas. Principalmente podemos observar claramente que el país que más días en activo mantiene bloques maliciosos de IPs es Uruguay. Uruguay precisamente no es uno de los países más problemáticos en este aspecto pero parece ser que tiene una política de eliminación de contenido malicioso en Internet bastante mala.
Otro conjunto interesante es el de Indonesia donde parece que tampoco hay mucha legislación para retirar contenido malicioso o si la hay no se realizan controles para su aplicación.
Podemos observar donde en España y en general en el resto de Europa, los bloques maliciosos apenas duran pocos días. Rusia o China a pesar de ser unos gigantes en cuanto a ataques maliciosos parece ser que tampoco mantienen en activo por mucho tiempo sus IPs problemáticas.
Este estudio de datos se podría usar para definir parámetros para intentar predecir la posibilidad que una IP se vaya a incluir en blacklist y en que categoría se incluiría.
Para poder entrenar un modelo de predicción hay que disponer de suficientes datos con atributos releventes diferenciados. De este modo y en base a los atributos se puede realizar la predicción.